﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Odbc;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using Msg.Encrypt.Attachment.Properties;
using MIME;
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.X509;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;

namespace Msg.Encrypt.Attachment
{
    /// <summary>
    ///     Beinhaltet die kryptografischen Methoden.
    /// </summary>
    public class Crypto
    {
        /// <summary>
        ///     Implementierung als Singleton.
        /// </summary>
        private static Crypto _instance;

        /// <summary>
        ///     Zertifikate, die im Source Code der Chain zur Überprüfung hinzugefügt werden.
        /// </summary>
        private readonly List<byte[]> _additionalCertificates;

        /// <summary>
        ///     Gibt an, ob es absolut im Batch-Modus laufen soll.
        ///     Das schließt auch immer quiet (q), force (f) und direct_send (d) mit ein.
        /// </summary>
        private bool _absolutBatchMode;

        /// <summary>
        ///     Der Text der E-Mail.
        /// </summary>
        private string _body = "";

        /// <summary>
        ///     Gibt an, ob die Datei sofort gesendet werden soll.
        /// </summary>
        private bool _directSend;

        /// <summary>
        ///     Eine optionale E-POSTBRIEF Adresse
        /// </summary>
        private string _epostAddress;

        /// <summary>
        ///     Liste der zu verschlüsselnden Dateien.
        /// </summary>
        private List<string> _fileNamesListToEncrypt;

        /// <summary>
        ///     Gibt an, ob Dateien zwingend überschrieben werden sollen, falls sie bereits vorhanden sind.
        /// </summary>
        private bool _force;

        /// <summary>
        ///     Gibt an, ob nur die MAPI getestet werden soll.
        /// </summary>
        private bool _mapiTest;

        /// <summary>
        ///     Gibt an, ob Fehlermeldungen entfallen sollen (silent mode).
        /// </summary>
        private bool _quiet;

        /// <summary>
        ///     Trigger, ob SplashScreen geschlossen werden soll (false).
        /// </summary>
        private bool _showSplash;

        /// <summary>
        ///     Der Betreff der E-Mail.
        /// </summary>
        private string _subject = "";

        /// <summary>
        ///     Konstrukotr, der die zur Überprüfung hinzuzuziehenden Stammzertifikate aus den Settings lädt.
        /// </summary>
        private Crypto()
        {
            _additionalCertificates = new List<byte[]>
            {
                Convert.FromBase64String(Settings.Default.RootCert),
                Convert.FromBase64String(Settings.Default.Cacert),
                Convert.FromBase64String(Settings.Default.RootCertTest)
            };
        }

        /// <summary>
        ///     Implementierung als Singleton
        /// </summary>
        public static Crypto Instance
        {
            get { return _instance ?? (_instance = new Crypto()); }
        }

        /// <summary>
        ///     Gibt zurück, ob die Argumente erfolgreich geparst wurden.
        /// </summary>
        /// <param name="args">Die zu parsenden Argumente</param>
        /// <returns>true, falls die Argumente erfolgreich geparst wurden und brauchbar sind.</returns>
        public bool ParseArguments(string[] args)
        {
            _fileNamesListToEncrypt = new List<string>();
            // Abfangen, dass keine Argumente übergeben wurden oder die p7m-Datei angeklickt wurde.
            if (args.Length == 0 || args[0].ToLower().EndsWith(Settings.Default.CryptoEnding))
            {
                ShowHelp();
                return false;
            }
            // Annahme: Array enthält Dateinamen, aber: es kann auch eine E-POSTBRIEF Adresse darunter sein.
            foreach (var arg in args)
            {
                // Ist es eine valide E-Mail-Adresse?
                if (IsValidMailAddress(arg))
                {
                    if (HasValidEPostbriefDomain(arg))
                    {
                        // Es handelt sich um eine E-POSTBRIEF Adresse
                        _epostAddress = arg;
                        Program.Log.Debug("E-POSTBRIEF Adresse: " + _epostAddress);
                    }
                    else
                    {
                        InformUser(
                            arg + "\nist keine gültige E-POSTBRIEF Adresse!",
                            "Ungültige E-POSTBRIEF Adresse", MessageBoxIcon.Error);
                    }
                }
                else if (arg.ToLower().StartsWith("-s=") || arg.ToLower().StartsWith("/s=") ||
                         arg.ToLower().StartsWith("s="))
                {
                    _subject = arg.Substring(arg.IndexOf("=", StringComparison.Ordinal) + 1);
                    Program.Log.Debug("Subject: " + _subject);
                }
                else if (arg.ToLower().StartsWith("-b=") || arg.ToLower().StartsWith("/b=") ||
                         arg.ToLower().StartsWith("b="))
                {
                    _body = arg.Substring(arg.IndexOf("=", StringComparison.Ordinal) + 1);
                    _body = _body.Replace("\\n", "\n");
                    Program.Log.Debug("Body: " + _body);
                }
                else if (arg.ToLower().Equals("-d") || arg.ToLower().Equals("/d") ||
                         arg.ToLower().Equals("d"))
                {
                    _directSend = true;
                    Program.Log.Debug("Direct Send: true");
                }
                else if (arg.ToLower().Equals("-a") || arg.ToLower().Equals("/a") ||
                         arg.ToLower().Equals("a"))
                {
                    _absolutBatchMode = true;
                    _quiet = true;
                    _force = true;
                    _directSend = true;
                    Program.Log.Debug("Absolute BatchMode: true");
                    Program.Log.Debug("Absolute BatchMode includes quiet, force, direct send.");
                }
                else if (arg.ToLower().Equals("-q") || arg.ToLower().Equals("/q") ||
                         arg.ToLower().Equals("q"))
                {
                    _quiet = true;
                    Program.Log.Debug("Quiet: true");
                }
                else if (arg.ToLower().Equals("-f") || arg.ToLower().Equals("/f") ||
                         arg.ToLower().Equals("f"))
                {
                    _force = true;
                    Program.Log.Debug("Force: true");
                }
                else if (arg.ToLower().Equals("-fq") || arg.ToLower().Equals("-qf") ||
                         arg.ToLower().Equals("fq") || arg.ToLower().Equals("qf"))
                {
                    _force = true;
                    _quiet = true;
                    Program.Log.Debug("Quiet and Force: true");
                }
                else if (arg.ToLower().Equals("-mt") || arg.ToLower().Equals("-mt") ||
                         arg.ToLower().Equals("mt") || arg.ToLower().Equals("mt"))
                {
                    _mapiTest = true;
                    Program.Log.Debug("Mapi Test: true");
                }
                else if (arg.Equals("?") || arg.ToLower().Equals("-h") ||
                         arg.ToLower().Equals("/h") ||
                         arg.ToLower().Equals("-help") ||
                         arg.ToLower().Equals("/help") ||
                         arg.ToLower().Equals("--help"))
                {
                    if (!_quiet)
                    {
                        ShowHelp();
                    }
                    return false;
                }
                else
                {
                    try
                    {
                        var fi = new FileInfo(arg);

                        if (fi.Exists)
                        {
                            var noOfPoints = fi.Name.Split('.').Length - 1;
                            if (Settings.Default.Check2Points && noOfPoints > 1)
                            {
                                InformUser(
                                    "Der Name der Datei\n" + fi.FullName +
                                    "\nenthält mehr als einen Punkt.\nEs ist nur ein Punkt erlaubt.\nDie Datei wird nicht verschlüsselt und versendet.",
                                    "Dateiname enthält mehr als einen Punkt!",
                                    MessageBoxIcon.Error);
                            }
                            else if (Settings.Default.Check40Chars && fi.Name.Length > 40)
                            {
                                InformUser(
                                    "Der Name der Datei\n" + fi.FullName +
                                    "\nist länger als 40 Zeichen.\nEs sind maximal 40 Zeichen erlaubt.\nDie Datei wird nicht verschlüsselt und versendet.",
                                    "Dateiname länger als 40 Zeichen!",
                                    MessageBoxIcon.Error);
                            }
                            else
                            {
                                _fileNamesListToEncrypt.Add(arg);
                            }
                        }
                        else
                        {
                            InformUser(
                                "Die Datei\n" + fi.FullName + "\nexistiert nicht.",
                                "Datei existiert nicht!", MessageBoxIcon.Error);
                        }
                    }
                    catch (Exception e)
                    {
                        InformUser(
                            "Mit der Datei\n" + arg + "\ngibt es Probleme:\n" +
                            e.Message, "Dateiprobleme!", MessageBoxIcon.Error);
                    }
                }
            }

            return true;
        }

        /// <summary>
        ///     Ruft die Verschlüsselung und die Mapi auf.
        /// </summary>
        public void Perform()
        {
            try
            {
                if (_fileNamesListToEncrypt == null || _fileNamesListToEncrypt.Count == 0)
                {
                    InformUser("Es wurden keine Dateien zum Versand übergeben.\nEs findet kein Versand statt!",
                        "Keine Dateien zum Versenden", MessageBoxIcon.Error);
                    return;
                }

                // Mapi-Test
                if (_mapiTest)
                {
                    Program.Log.Debug("Es wird der Mapi-Test ausgeführt.");
                    MapiCallTest();
                    return;
                }

                // Verschlüsselung
                var certificate = CheckEPostbriefAddressAndGetCertificate(_epostAddress);
                if (_absolutBatchMode)
                {
                    if (certificate == null)
                    {
                        InformUser(
                            "Für die von Ihnen (wenn überhaupt) angegebene E-POSTBRIEF Adresse konnte kein gültiges Zertifikat gefunden werden!\nDie Verarbeitung für diese Adresse wird abgebrochen!",
                            "Ungültige E-POSTBRIEF Adresse", MessageBoxIcon.Error);
                        return;
                    }
                }

                // Der Dialog zur Eingabe einer E-POSTBRIEF Adresse wird solange angezeigt,
                // bis eine gültige E-POSTBRIEF Adresse mit einem gültigen Zertifikat eingegeben wurde
                // oder der Vorgang abgebrochen wurde.
                while (certificate == null)
                {
                    var mf = new MainForm();
                    var res = mf.ShowDialog();
                    switch (res)
                    {
                        case DialogResult.OK:
                            _epostAddress = mf.EPostBriefAdresse;
                            if (_epostAddress.Length > 0 && _epostAddress.IndexOf('@') < 1)
                            {
                                _epostAddress += Settings.Default.StandardEPOSTDomain;
                            }
                            certificate = CheckEPostbriefAddressAndGetCertificate(_epostAddress);
                            break;
                        case DialogResult.Cancel:
                            return;
                    }
                }

                // Es findet eine Validierung der kompletten Zertifikatskette statt.
                // Sollte ein Fehler vorhanden sein, kann unter dem Umstand, dass es sich um eine Test-Domain handelt und der Parameter Settings.Default.AllowManualOverrideForSignatureCheckingForTestDomains
                // true ist, eine Abfrage erfolgen, ob trotzdem versendet wird.
                var overrideCertifacteError = HasValidEPostbriefTestDomain(_epostAddress) && Settings.Default.AllowManualOverrideForSignatureCheckingForTestDomains;
                if (!ValidateCertificateSignature(certificate, overrideCertifacteError))
                {
                    InformUser("Der Versand zur E-POSTBRIEF Adresse\n'" + _epostAddress + "'\nwird abgebrochen!",
                        "Versand abgebrochen", MessageBoxIcon.Error);
                    return;
                }

                // Während der Verschlüsselung bis nach dem Abschluss des MAPI-Vorgangs wird eine SplashScreen angezeigt.
                ShowSplashScreen();

                // Verschlüsselt die Datei und liefert den Dateinamen der verschlüsselten Datei zurück.
                var newfiles = EncryptMessageBc(_fileNamesListToEncrypt, certificate);

                // Übergabe an die MAPI-SChnittstelle
                var mapi = new Mapi.Mapi();
                mapi.Reset();
                mapi.AddRecip(_epostAddress, null, false);
                foreach (var newfile in newfiles)
                {
                    Program.Log.Info("Die Datei " + newfile + " wird als Attachment angehängt.");
                    mapi.Attach(newfile);
                }

                int ok;

                if (!String.IsNullOrEmpty(_subject))
                {
                    Program.Log.Info("Es wurde folgendes Betreff übergeben: " + _subject);
                }

                if (!String.IsNullOrEmpty(_body))
                {
                    Program.Log.Info("Es wurde folgender Text übergeben: " + _body);
                }

                if (_directSend)
                {
                    Program.Log.Debug("Es wird direkt ohne Anzeige des E-Mail-Client-Fensters versendet.");
                    // Versand ohne Mail-Client-Fenster
                    ok = mapi.SendQuiet(_subject, _body);
                }
                else
                {
                    Program.Log.Debug("Es wird das E-Mail-Client-Fenster agenzeigt.");
                    // Versand mit Mail-Client-Fenster
                    ok = mapi.Send(_subject, _body);
                }

                // SplashScreen wird geschlossen.
                HideSplashScreen();

                // Zuweisung der Fehlermeldung aufgrund des MAPI-Rückgabewerts.
                var errorString = new string[28];
                errorString[0] =
                    "Der E-POSTBRIEF wurde zum Versand an die Adresse '{0}' an das E-Mail Programm übergeben!";
                //     WHEN  1 THEN RESULT = "User Abort".
                errorString[1] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde vom Benutzer abgebrochen!";
                //     WHEN  2 THEN RESULT = "Failure".
                errorString[2] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde mit einem Fehler abgebrochen!";
                //     WHEN  3 THEN RESULT = "Login Failure".
                errorString[3] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde aufgrund eines Login Fehlers abgebrochen!";
                //     WHEN  4 THEN RESULT = "Disk Full".
                errorString[4] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Der Speicherplatz ist voll!";
                //     WHEN  5 THEN RESULT = "Insufficient Memory".
                errorString[5] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Es ist nicht genügend Arbeitsspeicher vorhanden!";
                //     WHEN  6 THEN RESULT = "Blk Too Small".
                errorString[6] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Blk zu klein!";
                errorString[7] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Blk zu klein! Seltsamer Fehler!";
                //     WHEN  8 THEN RESULT = "Too Many Sessions".
                errorString[8] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Zu viele offen Sitzungen!";
                //     WHEN  9 THEN RESULT = "Too Many Files".
                errorString[9] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Zu viele Dateien!";
                //     WHEN 10 THEN RESULT = "Too Many Recipients".
                errorString[10] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Zu viele Empfänger!";
                //     WHEN 11 THEN RESULT = "Attachment Not Found".
                errorString[11] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Mindestens ein Anhang wurde nicht gefunden!";
                //     WHEN 12 THEN RESULT = "Attachment Open Failure".
                errorString[12] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Mindestens ein Anhang konnte nicht geöffnet werden!";
                //     WHEN 13 THEN RESULT = "Attachment Write Failure".
                errorString[13] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Mindestens ein Anhang konnte nicht geschrieben werden!";
                //     WHEN 14 THEN RESULT = "Unknown Recipient".
                errorString[14] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Unbekannter Empfänger!";
                //     WHEN 15 THEN RESULT = "Bad Recipient type".
                errorString[15] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Fehlerhafter Empfängertyp";
                //     WHEN 16 THEN RESULT = "No Messages".
                errorString[16] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Keine Nachricht vorhanden!";
                //     WHEN 17 THEN RESULT = "Invalid Message".
                errorString[17] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Ungültige Nachricht!";
                //     WHEN 18 THEN RESULT = "Bodytext Too Large".
                errorString[18] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Text der E-Mail zu lang!";
                //     WHEN 19 THEN RESULT = "Invalid Session".
                errorString[19] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Ungültuge Sitzung!";
                //     WHEN 20 THEN RESULT = "Type Not Supported".
                errorString[20] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Typ nicht unterstützt!";
                //     WHEN 21 THEN RESULT = "Ambiguous Recipient".
                errorString[21] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Mehrdeutiger Empfänger!";
                //     WHEN 22 THEN RESULT = "Message in use".
                errorString[22] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Nachricht wird bereits benutzt!";
                //     WHEN 23 THEN RESULT = "Network failure".
                errorString[23] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Netzwerkfehler!";
                //     WHEN 24 THEN RESULT = "Invalid edit fields".
                errorString[24] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Ungültige Felder!";
                //     WHEN 25 THEN RESULT = "Invalid recipients".
                errorString[25] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Ungültige Empfänger";
                //     WHEN 26 THEN RESULT = "Feature not supported"
                errorString[26] =
                    "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Merkmal nicht unterstützt!";
                //     OTHERWISE RESULT    = "Unknown error".
                errorString[27] = "Der Versand an die E-POSTBRIEF Adresse '{0}' wurde abgebrochen! Unbekannte Fehler";

                if (ok == 0)
                {
                    if (Settings.Default.ShowOKMessageBox)
                    {
                        InformUser(string.Format(errorString[ok], _epostAddress), "Versand angestoßen", MessageBoxIcon.None);
                    }
                }
                else if (ok < 27)
                {
                    InformUser(string.Format(errorString[ok], _epostAddress), "Abbruch beim Versenden",
                        MessageBoxIcon.Warning);
                }
                else
                {
                    InformUser(string.Format(errorString[27], _epostAddress), "Abbruch beim Versenden",
                        MessageBoxIcon.Warning);
                }
            }
            catch (FileNotFoundException fnf)
            {
                HideSplashScreen();
                // Datei wurde nicht gefunden
                InformUser("Die angegebene Datei\n" + fnf.FileName + "\nkonnte nicht gefunden werden!",
                    "Datei nicht vorhanden", MessageBoxIcon.Error);
            }
            catch (PathTooLongException ptle)
            {
                HideSplashScreen();
                // Pfad zu lang
                InformUser("Der Pfad der angegebene Datei ist zu lang!\n" + ptle.Message, "Pfad zu lang.",
                    MessageBoxIcon.Error);
            }
            catch (IOException ioe)
            {
                HideSplashScreen();
                // Fehler beim erzeugen der Datei
                InformUser(
                    "Es ist ein Fehler beim Verarbeiten der Datei aufgetreten:\n" + ioe.Message +
                    "\nDer Vorgang wurde abgebrochen.", "Dateiverarbeitungsfehler", MessageBoxIcon.Error);
            }
            catch (Exception ex)
            {
                HideSplashScreen();
                // Alle anderen Fehler
                InformUser(
                    ex.Message + " Zur angegebenen Adresse\n'" + _epostAddress +
                    "'\nkonnte keine Verschlüsselung stattfinden!", "Keine Verschlüsselung möglich", MessageBoxIcon.Error);
                try
                {
                    if ((bool) Settings.Default["sqldebug"])
                    {
                        InformUser(ex.StackTrace, "SQL Debug: " + ex.Message, MessageBoxIcon.Error);
                    }
                }
                catch (SettingsPropertyNotFoundException)
                {
                    // Das kann passieren, wenn sqldebug nicht in den Settings ist. Das ist aber nicht schlimm.
                    // Dann möchte man ja auch kein debug.
                }
            }
        }

        /// <summary>
        ///     Prüft, ob es sich um eine gültige Domäne handelt.
        /// </summary>
        /// <param name="toBeChecked">Zu prüfende E-POSTBRIEF Adresse</param>
        /// <param name="domains">Zur Prüfung hernazuziehende Domänen</param>
        /// <returns>true, false</returns>
        private static bool HasValdiDomain(string toBeChecked, string domains)
        {
            if (toBeChecked == null)
            {
                return false;
            }

            // Normierung auf Kleinbuchstaben
            toBeChecked = toBeChecked.ToLower();

            // Check auf in der Konfiguration hinterlegte Domainnamen (diese müssen mit Kleinbuchstaben und ;separiert in der Konfiguration abgelegt werden.
            var epostDomains = domains.Split(';');
            var isValidDomain = epostDomains.Aggregate(false, (current, t) => current || toBeChecked.EndsWith(t));
            return isValidDomain;
        }

        /// <summary>
        ///     Prüft, ob es sich um eine gültige produktive E-POSTBRIEF Adresse handelt.
        /// </summary>
        /// <param name="toBeChecked">Zu prüfende E-POSTBRIEF Adresse</param>
        /// <returns>true, false</returns>
        private static bool HasValidEPostbriefProdDomain(string toBeChecked)
        {
            return HasValdiDomain(toBeChecked, Settings.Default.EPOSTDomains);
        }

        /// <summary>
        ///     Prüft, ob es sich um eine gültige Test E-POSTBRIEF Adresse handelt.
        /// </summary>
        /// <param name="toBeChecked">Zu prüfende Test E-POSTBRIEF Adresse</param>
        /// <returns>true, false</returns>
        private static bool HasValidEPostbriefTestDomain(string toBeChecked)
        {
            return HasValdiDomain(toBeChecked, Settings.Default.EPOSTTestDomains);
        }

        /// <summary>
        ///     Prüft, ob es sich um eine gültige E-POSTBRIEF Adresse (produktiv oder Test) handelt.
        /// </summary>
        /// <param name="toBeChecked">Zu prüfende Test E-POSTBRIEF Adresse</param>
        /// <returns>true, false</returns>
        private static bool HasValidEPostbriefDomain(string toBeChecked)
        {
            return HasValidEPostbriefProdDomain(toBeChecked) || HasValidEPostbriefTestDomain(toBeChecked);
        }

        /// <summary>
        ///     Prüft, ob die Adresse eine valide E-POSTBRIEF Domäne besitzt.
        /// </summary>
        /// <param name="toBeChecked">Zu prüfende E-POSTBRIEF Adresse</param>
        /// <returns>true, false</returns>
        private bool IsValidMailAddress(string toBeChecked)
        {
            if (toBeChecked == null)
            {
                return false;
            }

            Regex mailRegexp;

            // Normierung auf Kleinbuchstaben
            toBeChecked = toBeChecked.ToLower();

            try
            {
                mailRegexp = new Regex(Settings.Default.mailRegexp);
            }
            catch (Exception)
            {
                InformUser("Konfiguration der Adressprüfung fehlerhaft!\nBitte kontaktieren Sie Ihren Administrator",
                    "Konfiguration fehlerhaft", MessageBoxIcon.Error);
                return false;
            }

            return mailRegexp.IsMatch(toBeChecked);
        }

        /// <summary>
        ///     Verschlüsselt die in der Liste angegebenen Dateien mit dem angegebenen Zertifikat und liefert eine Liste mit den
        ///     Dateinamen der verschlüsselten Dateien zurück.
        /// </summary>
        /// <param name="paths">Pfade, der zu verschlüsselnden Dateien.</param>
        /// <param name="certificate">Öffentliches Zertifikat, das zum Verschlüsseln genutzt werden soll.</param>
        /// <returns>Liefert die Liste der Pfade der verschlüsselten Dateien zurück.</returns>
        private IEnumerable<string> EncryptMessageBc(ICollection<string> paths, X509Certificate certificate)
        {
            Program.Log.Debug("Anzahl der zu verschlüsselnden Dateien: " + paths.Count);
            return paths.Select(path => EncryptMessageBc(path, certificate)).ToList();
        }

        /// <summary>
        ///     Verschlüsselt eine Nachricht anhand des übergebenen öffentlichen Schlüssels.
        /// </summary>
        /// <param name="fullFileName">Pfad der zu verschlüsselnden Datei</param>
        /// <param name="certificate">Öffentlicher Schlüssel</param>
        /// <returns>Pfad der verschlüsselten Datei</returns>
        private string EncryptMessageBc(string fullFileName, X509Certificate certificate)
        {
            Program.Log.Debug("Eingabedatei: " + fullFileName);
            Program.Log.Debug("Seriennummer des Zertifikats: " + certificate.SerialNumber);

            // Dateiinfo über zu verschlüsselnde Datei
            var fileInfo = new FileInfo(fullFileName);

            // Mime codieren
            var mm = new MimeMessage();
            var id = DateTime.Now.ToString("yyyyMMddHHmmssfff");
            mm.SetParameter("Message-ID", "Message-ID", id);
            mm.Setversion();
            mm.SetContentType("multipart/mixed");
            mm.SetBoundary("----=_Part_0_" + id);
            var body = mm.CreatePart();
            body.ReadFromFile(fileInfo.FullName);

            body.SetDisposition("attachment;filename=" + fileInfo.Name, null);

            var message = new StringBuilder();
            mm.StoreBody(message);

            // Debug
            try
            {
                if ((bool) Settings.Default["mimedebug"])
                {
                    File.WriteAllText(fileInfo.FullName + Settings.Default.MimeEnding, message.ToString());
                }
            }
            catch (SettingsPropertyNotFoundException)
            {
                // Das kann passieren, wenn sqldebug nicht in den Settings ist. Das ist aber nicht schlimm.
                // Dann möchte man ja auch kein debug.
            }

            // zu verschlüsselnde Bytes der Mime-codierten Datei
            var bytesToEncrypt = new ASCIIEncoding().GetBytes(message.ToString());

            // Begin Verschlüsselung
            var edGen = new CmsEnvelopedDataGenerator();
            edGen.AddKeyTransRecipient(certificate);
            var ed = edGen.Generate(
                new CmsProcessableByteArray(bytesToEncrypt),
                CmsEnvelopedGenerator.Aes256Cbc);

            var outBytes = ed.GetEncoded();
            // Ende Verschlüsselung, Daten sind nun in outBytes verschlüsselt

            // Dateiname der Datei mit den verschlüsselten Daten
            var newFilename = CreateCryptoFileName(fileInfo.FullName);

            // Abfrage, ob Datei überschrieben werden soll, fall sie schon existiert, aber nur dann, wenn wir keinen force mode haben
            if (File.Exists(newFilename) && !_force)
            {
                var question = "Die verschlüsselte Datei " + newFilename +
                               " existiert bereits. Soll sie überschrieben werden?";
                Program.Log.Info(question);
                HideSplashScreen();
                var res = MessageBox.Show(null, question, @"Datei bereits vorhanden",
                    MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
                switch (res)
                {
                    case DialogResult.Cancel:
                        Program.Log.Info("Abbruch");
                        throw new IOException(newFilename);
                    case DialogResult.Yes:
                        // Datei mit verschlüsselten Daten schreiben
                        Program.Log.Info("Ja");
                        File.WriteAllBytes(newFilename, outBytes);
                        Program.Log.Debug("Ausgabedatei: " + newFilename);
                        break;
                    case DialogResult.No:
                        Program.Log.Info("Nein");
                        break;
                }
                ShowSplashScreen();
            }
            else
            {
                // Datei mit verschlüsselten Daten schreiben
                File.WriteAllBytes(newFilename, outBytes);
            }

            return newFilename;
        }

        /// <summary>
        ///     Erzeugt den Dateinamen der verschlüsselten Datei aus dem Original-Dateinamen.
        ///     Die originale Dateiendung wird durch CRYPTO_ENDING ersetzt, falls Settings.Default.AppendExtension = False.
        ///     Falls Settings.Default.AppendExtension = True, wird dien Endung hinten dran gehängt.
        /// </summary>
        /// <param name="fullFileName">Originaler Dateiname</param>
        /// <returns>Dateiname der verschlüsselten Datei</returns>
        private static string CreateCryptoFileName(string fullFileName)
        {
            var fi = new FileInfo(fullFileName);
            if (Settings.Default.AppendCryptoEnding)
            {
                return fullFileName + Settings.Default.CryptoEnding;
            }
            return fullFileName.Replace(fi.Extension, Settings.Default.CryptoEnding);
        }

        /// <summary>
        ///     Führt die Validierung der kompletten Zertifikatskette durch.
        /// </summary>
        /// <param name="certificate">Das zu prüfende Zertifikat.</param>
        /// <param name="overrideCertificateError">Gibt an, ob trotz Zertifikatfehler eine Abfrage erfolgen soll, ob trotzdem versendet werden soll.</param>
        /// <returns>true/false</returns>
        private bool ValidateCertificateSignature(X509Certificate certificate, bool overrideCertificateError)
        {
            var chain = new X509Chain();
            // Hier fügen wir der Zertifikatskette standardmäßig unser Root und unser CA hinzu.
            foreach (var cert in _additionalCertificates.Select(x => new X509Certificate2(x)))
            {
                chain.ChainPolicy.ExtraStore.Add(cert);
            }

            // Das hier prüft die komplette Zertifikatskette inklusive Signaturen, Gültigkeiten.
            // Es findet kein Revocation Check statt.
            chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
            chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
            var primaryCert = new X509Certificate2(certificate.GetEncoded());

            var retValue = chain.Build(primaryCert);

            if (chain.ChainStatus.Length == 0 && retValue)
            {
                return true;
            }

            // Falls in den Fehlermeldungen genau der Fehler "UntrustedRoot" vorkommt, dann fragen wir, ob trotzdem versendet werden soll.
            // In diesem Fall wurde die digitale Signatur mittels Root-Zertifikat erfolgreich geprüft, sonst wäre ein weiterer Fehler vorhanden, der anzeigt, das die Signaturprüfung nicht erfolgreich war.
            if (chain.ChainStatus.Length == 1 &&
                (chain.ChainStatus[0].Status.Equals(X509ChainStatusFlags.UntrustedRoot)))
            {
                var rootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
                var message = new StringBuilder("Das Root-Zertifikat")
                    .Append(Environment.NewLine)
                    .Append(rootCert.Issuer)
                    .Append(Environment.NewLine)
                    .Append("mit dem Fingerabdruck")
                    .Append(Environment.NewLine)
                    .Append(rootCert.Thumbprint)
                    .Append(Environment.NewLine)
                    .Append(
                        "des öffentlichen Schlüssel des Empfängers wurde nicht in den vertrauenswürdigen Stammzertifikaten gefunden!")
                    .Append(Environment.NewLine)
                    .Append("Möchten Sie dennoch verschlüsseln und versenden?");

                return AskYesNoQuestion(message.ToString(), "Kein vertrauenswürdiges Root-Zertifikat");
            }
            // Falls in den Fehlermeldungen genau der Fehler "PartialChain" vorkommt, dann fragen wir, ob trotzdem versendet werden soll.
            // In diesem Fall findet keine Signaturprüfung statt.
            if (chain.ChainStatus.Length == 1 && (chain.ChainStatus[0].Status.Equals(X509ChainStatusFlags.PartialChain)))
            {
                var message = new StringBuilder("Das Root-Zertifikat")
                    .Append(Environment.NewLine)
                    .Append(certificate.IssuerDN)
                    .Append(Environment.NewLine)
                    .Append("des öffentlichen Schlüssel des Empfängers konnte nicht gefunden werden!")
                    .Append(Environment.NewLine)
                    .Append("Möchten Sie dennoch verschlüsseln und versenden?");

                return AskYesNoQuestion(message.ToString(), "Kein Root-Zertifikat");
            }

            if (chain.ChainStatus.Length <= 0)
            {
                return retValue;
            }

            // Ansonsten zeigen wir alle Fehler in der Zertifizierungskette an und gebenbis auf den override Fall den fehlerhaften Status zurück.
            for (var i = 0; i < chain.ChainStatus.Length; i++)
            {
                InformUser(chain.ChainStatus[i].StatusInformation,
                    String.Format("Fehler {0}/{1}: {2}", i + 1, chain.ChainStatus.Length,
                        chain.ChainStatus[i].Status), MessageBoxIcon.Error);
            }

            // Die Abfrage wird auch angezeigt, falls es sich um eine Testadresse handelt und der Parameter Settings.Default.AllowManualOverrideForSignatureCheckingForTestDomains auf true steht
            if (overrideCertificateError)
            {
                var message = new StringBuilder("Es sind Zertifikatsfehler für das folgende Terztifikat vorhanden:")
                    .Append(Environment.NewLine)
                    .Append(certificate.IssuerDN)
                    .Append(Environment.NewLine)
                    .Append("Möchten Sie dennoch verschlüsseln und versenden?");

                return AskYesNoQuestion(message.ToString(), "Zertifikatsfehler");
            }

            return false;
        }

        /// <summary>
        ///     Zu gegebener E-POSTBRIEF Adresse wird aus der Datenbank der öffentliche Schlüssel ausgelesen
        /// </summary>
        /// <param name="ePostBriefAdresse">E-POSTBRIEF Adresse</param>
        /// <returns>Öffentlicher Schlüssel als Zertifikatsobjekt</returns>
        private X509Certificate GetCertificateFromEpostBriefAdresseBc(string ePostBriefAdresse)
        {
            X509Certificate z = null;
            // ODBC Connection String aus Konfigurationsdatei auslesen
            var connectionString =
                ConfigurationManager.ConnectionStrings["Msg.Encrypt.Attachment.Properties.Settings.e2eConnection"].ConnectionString;
            // Select Statement aus Konfigurationsdatei auslesen
            var selectString = Settings.Default.select;

            Program.Log.Debug("ConnectionsString: " + connectionString);
            Program.Log.Debug("SelectString: " + selectString);

            // ODBC Abfrage ausführen
            using (var con = new OdbcConnection(connectionString))
            {
                con.Open();
                using (var com = new OdbcCommand(selectString, con))
                {
                    try
                    {
                        com.Parameters.AddWithValue("@var", ePostBriefAdresse.ToLower());
                        var zertifikat = com.ExecuteScalar().ToString();
                        var cf = new X509CertificateParser();
                        z = cf.ReadCertificate(Convert.FromBase64String(zertifikat));
                        if (z != null)
                        {
                            Program.Log.Debug("Zertifikat gefuden: Seriennummer: " + z.SerialNumber);
                        }
                        else
                        {
                            Program.Log.Debug("Es konnte kein Zertifikat gefunden werden");
                        }
                    }
                    catch (Exception ex)
                    {
                        // Falls in der Konfigurationsdatei sqldebug geseztzt wurde, werden die entsprechenden Fehlermeldungen angezeigt.
                        try
                        {
                            if ((bool) Settings.Default["sqldebug"])
                            {
                                InformUser(ex.StackTrace, "SQL Debug: " + ex.Message, MessageBoxIcon.Error);
                                InformUser(com.CommandText, "SQL Debug: SQL Command", MessageBoxIcon.Error);
                            }
                        }
                        catch (SettingsPropertyNotFoundException)
                        {
                            // Das kann passieren, wenn sqldebug nicht in den Settings ist. Das ist aber nicht schlimm.
                            // Dann möchte man ja auch kein debug.
                        }
                    }
                }
            }
            return z;
        }

        /// <summary>
        ///     Prüft zunächst die E-POSTBRIEF Adresse und versucht dann anhand der E-POSTBRIEF Adresse eine Zertifikat zu
        ///     erhalten.
        /// </summary>
        /// <param name="address">E-POSTBRIEF Adresse</param>
        /// <returns>Zertifikat zur E-POSTBRIEF Adresse</returns>
        private X509Certificate CheckEPostbriefAddressAndGetCertificate(string address)
        {
            X509Certificate certificate = null;

            if (address == null)
            {
                if (_absolutBatchMode)
                {
                    InformUser("Sie haben keine E-POSTBRIEF Adresse eingegeben!", "Keine E-POSTBRIEF Adresse",
                        MessageBoxIcon.Error);
                }
            }
            else if (!HasValidEPostbriefDomain(address) || !IsValidMailAddress(address))
            {
                InformUser(
                    address + "\nist keine gültige E-POSTBRIEF Adresse!\nBitte geben Sie die Adresse erneut ein.",
                    "Ungültige E-POSTBRIEF Adresse", MessageBoxIcon.Error);
            }
            else if (_epostAddress.Contains(";") || _epostAddress.Contains(",") || _epostAddress.Contains(" "))
            {
                InformUser(
                    "Es scheint so, als hätten Sie mehrere E-POSTBRIEF Adressen angegeben:\n'" + address +
                    "'\nSie können nur an eine E-POSTBRIEF Adresse gleichzeitig versenden!",
                    "Zu viele E-POSTBRIEF Adressen", MessageBoxIcon.Error);
                _epostAddress = null;
            }
            else
            {
                // Holt sich das Zertifkat aus der Datenbank.
                certificate = GetCertificateFromEpostBriefAdresseBc(_epostAddress);
                // Fehlermeldung, falls kein Zertifikat gefunden wurde.
                if (certificate == null)
                {
                    InformUser(
                        "Zur angegebenen E-POSTBRIEF Adresse\n'" + _epostAddress +
                        "'\nkonnte kein gültiges Zertifikat gefunden werden!", "Kein Zertifikat", MessageBoxIcon.Error);
                }
            }

            return certificate;
        }

        /// <summary>
        ///     Testet die Mapi-Schnittstelle.
        /// </summary>
        private void MapiCallTest()
        {
            Program.Log.Debug("Starte Mapi-Test");
            // Übergabe an die MAPI-Schnittstelle
            var mapi = new Mapi.Mapi();
            mapi.Reset();
            Program.Log.Debug("An Mapi wird folgende Adresse übergeben: " + _epostAddress);
            mapi.AddRecip(_epostAddress, null, false);
            foreach (var file in _fileNamesListToEncrypt)
            {
                Program.Log.Debug("File: " + file);
                mapi.Attach(file);
            }

            // Versand mit Mail-Client-Fenster
            var ok = mapi.Send(_subject, _body);

            InformUser("Mapi retval: " + ok, "Mapi", MessageBoxIcon.Hand);
            Program.Log.Debug("Beende Mapi-Test");
        }

        /// <summary>
        ///     Schließt den SplashScreen.
        /// </summary>
        private void HideSplashScreen()
        {
            if (!_quiet && !_absolutBatchMode)
            {
                Program.Log.Debug("Splashscreen soll geschlossen werden");
            }
            _showSplash = false;
        }

        /// <summary>
        ///     Zeigt den SplashScreen an, bis hideSplashScreen() aufgerufen wird.
        /// </summary>
        private void ShowSplashScreen()
        {
            if (_quiet || _absolutBatchMode)
            {
                return;
            }
            _showSplash = true;
            Program.Log.Debug("Splashscreen wird angezeigt");
            ThreadPool.QueueUserWorkItem(x =>
            {
                using (var splashForm = new Splash())
                {
                    splashForm.Show();
                    while (_showSplash)
                    {
                        Application.DoEvents();
                    }
                    Program.Log.Debug("Splashscreen wird geschlossen");
                    splashForm.Close();
                }
            });
        }

        /// <summary>
        ///     Zeigt die Hilfefunktion an.
        /// </summary>
        private static void ShowHelp()
        {
            var helpMessage = new StringBuilder("Aufruf:").Append(Environment.NewLine)
                .Append(
                    "[Pfad\\]Verschluesselung.exe [-h] [-f] [-q] [-a] [-s=\"<subject>\"] [-b=\"<body>\"] [E-POSTBRIEF Adresse] Dateiname [weitere Dateinamen...]")
                .Append(Environment.NewLine)
                .Append(Environment.NewLine)
                .Append("-h help:                  Zeigt diese Hilfemeldung an.").Append(Environment.NewLine)
                .Append(
                    "-f force:                 Überspringt die Abfrage nach bereits vorhandenen verschlüsselten Dateien und verschlüsselt")
                .Append(Environment.NewLine)
                .Append(
                    "                          und überschreibt die eventuell vorhandene Datei mit dem zur E-POSTBRIEF Adresse")
                .Append(Environment.NewLine)
                .Append("                          passenden Schlüssel.").Append(Environment.NewLine)
                .Append(
                    "-q quiet:                 Unterdrückt auftretenden Fehlermeldungen. Die einzige Abfrage, die angezeigt wird,")
                .Append(Environment.NewLine)
                .Append(
                    "                          ist die nach der E-POSTBRIEF Adresse, falls keine als Argument mitgegeben wurde.")
                .Append(Environment.NewLine)
                .Append(
                    "-a absoluter Batchmodus:  Die angegebenen Dateien werden verschlüsselt und an den angegebenen Empfänger verschickt")
                .Append(Environment.NewLine)
                .Append("                          (der zwingend angegeben sein muss").Append(Environment.NewLine)
                .Append("-s=\"<subject>\" subject:   Hier kann der Betreff des E-POSTBRIEFs angegeben werden.")
                .Append(Environment.NewLine)
                .Append(
                    "-b=\"<body>\" body:         Hier kann der Text der E-Mail angegeben werden. Zeilenumbrüche werden mit '\\n' markiert.")
                .Append(Environment.NewLine)
                .Append("                          Es ist nur Text möglich, kein html oder rtf.")
                .Append(Environment.NewLine)
                .Append("[E-POSTBRIEF Adresse]     optionale Angabe einer E-POSTBRIEF Adresse, die dazu führt,")
                .Append(Environment.NewLine)
                .Append("                          dass nicht mehr nach einer E-POSTBRIEF Adresse gefragt wird.")
                .Append(Environment.NewLine)
                .Append(
                    "Dateiname                 Angabe des Namens einer Datei, die mit dem zur EPostbriefadresse passenden")
                .Append(Environment.NewLine)
                .Append(
                    "                          öffentlichen Schlüssel verschlüsselt werden soll und als Attachment an den E-POSTBRIEF")
                .Append(Environment.NewLine)
                .Append(
                    "                          angehängt werden soll. Die verschlüsselte Datei wird im gleichen Verzeichnis mit der")
                .Append(Environment.NewLine)
                .Append("                          Endung .p7m erzeugt.").Append(Environment.NewLine)
                .Append(
                    "[Dateinamen...]           optional weitere Namen von zu verschlüsselnden und anzuhängenden Dateien.");

            var helpForm = new HelpForm {richTextBox1 = {Text = helpMessage.ToString()}};
            Program.Log.Warn(helpMessage);
            helpForm.ShowDialog();
        }

        /// <summary>
        ///     Stellt eine Ja/Nein Frage und gibt die Antwort als bool zurück.
        /// </summary>
        /// <param name="message">Die angezeigte Meldung</param>
        /// <param name="caption">Der Fenstertitel</param>
        /// <returns>true/false</returns>
        private static bool AskYesNoQuestion(string message, string caption)
        {
            Program.Log.Info(caption + ": " + message);
            var res = MessageBox.Show(message, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
            Program.Log.Info("Antwort: " + res);
            return res == DialogResult.Yes;
        }

        /// <summary>
        ///     Führt das Logging durch und zeigt je nach Anwendungsfall eine Meldung und loggt es in die Log-Datei.
        /// </summary>
        /// <param name="message">Nachricht der Meldung</param>
        /// <param name="caption">Fenstertitel der Meldung</param>
        /// <param name="icon">MessageBoxIcon mit dem die Meldung angezeigt werden soll.</param>
        public void InformUser(string message, string caption, MessageBoxIcon icon)
        {
            if (!_quiet)
            {
                MessageBox.Show(message, caption, MessageBoxButtons.OK, icon);
            }
            switch (icon)
            {
                case MessageBoxIcon.Error:
                    Program.Log.Error(message);
                    break;
                case MessageBoxIcon.Warning:
                    Program.Log.Warn(message);
                    break;
                case MessageBoxIcon.Information:
                case MessageBoxIcon.None:
                case MessageBoxIcon.Question:
                    Program.Log.Info(message);
                    break;
                default:
                    Program.Log.Info(message);
                    break;
            }
        }
    }
}